// Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors
// Distributed under MIT license, or public domain if desired and
// recognized in your jurisdiction.
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE

#if !defined(JSON_IS_AMALGAMATION)
#include "json_tool.h"
#include "../writer.h"
#endif // if !defined(JSON_IS_AMALGAMATION)
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cstring>
#include <iomanip>
#include <memory>
#include <set>
#include <sstream>
#include <utility>

#if __cplusplus >= 201103L
#include <cmath>
#include <cstdio>

#if !defined(isnan)
#define isnan std::isnan
#endif

#if !defined(isfinite)
#define isfinite std::isfinite
#endif

#else
#include <cmath>
#include <cstdio>

#if defined(_MSC_VER)
#if !defined(isnan)
#include <float.h>
#define isnan _isnan
#endif

#if !defined(isfinite)
#include <float.h>
#define isfinite _finite
#endif

#if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES)
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
#endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES

#endif //_MSC_VER

#if defined(__sun) && defined(__SVR4) // Solaris
#if !defined(isfinite)
#include <ieeefp.h>
#define isfinite finite
#endif
#endif

#if defined(__hpux)
#if !defined(isfinite)
#if defined(__ia64) && !defined(finite)
#define isfinite(x)                                                            \
  ((sizeof(x) == sizeof(float) ? _Isfinitef(x) : _IsFinite(x)))
#endif
#endif
#endif

#if !defined(isnan)
// IEEE standard states that NaN values will not compare to themselves
#define isnan(x) ((x) != (x))
#endif

#if !defined(__APPLE__)
#if !defined(isfinite)
#define isfinite finite
#endif
#endif
#endif

#if defined(_MSC_VER)
// Disable warning about strdup being deprecated.
#pragma warning(disable : 4996)
#endif

namespace Json {
#if __cplusplus >= 201103L || (defined(_CPPLIB_VER) && _CPPLIB_VER >= 520)
	using StreamWriterPtr = std::unique_ptr<StreamWriter>;
#else
	using StreamWriterPtr = std::auto_ptr<StreamWriter>;
#endif

	String valueToString(LargestInt value) {
		UIntToStringBuffer buffer;
		char* current = buffer + sizeof(buffer);
		if (value == Value::minLargestInt) {
			uintToString(LargestUInt(Value::maxLargestInt) + 1, current);
			*--current = '-';
		}
		else if (value < 0) {
			uintToString(LargestUInt(-value), current);
			*--current = '-';
		}
		else {
			uintToString(LargestUInt(value), current);
		}
		assert(current >= buffer);
		return current;
	}

	String valueToString(LargestUInt value) {
		UIntToStringBuffer buffer;
		char* current = buffer + sizeof(buffer);
		uintToString(value, current);
		assert(current >= buffer);
		return current;
	}

#if defined(JSON_HAS_INT64)

	String valueToString(Int value) { return valueToString(LargestInt(value)); }

	String valueToString(UInt value) { return valueToString(LargestUInt(value)); }

#endif // # if defined(JSON_HAS_INT64)

	namespace {
		String valueToString(double value, bool useSpecialFloats,
			unsigned int precision, PrecisionType precisionType) {
			// Print into the buffer. We need not request the alternative representation
			// that always has a decimal point because JSON doesn't distinguish the
			// concepts of reals and integers.
			if (!isfinite(value)) {
				static const char* const reps[2][3] = { {"NaN", "-Infinity", "Infinity"},
													   {"null", "-1e+9999", "1e+9999"} };
				return reps[useSpecialFloats ? 0 : 1]
					[isnan(value) ? 0 : (value < 0) ? 1 : 2];
			}

			String buffer(size_t(36), '\0');
			while (true) {
				int len = jsoncpp_snprintf(
					&*buffer.begin(), buffer.size(),
					(precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f",
					precision, value);
				assert(len >= 0);
				auto wouldPrint = static_cast<size_t>(len);
				if (wouldPrint >= buffer.size()) {
					buffer.resize(wouldPrint + 1);
					continue;
				}
				buffer.resize(wouldPrint);
				break;
			}

			buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end());

			// try to ensure we preserve the fact that this was given to us as a double on
			// input
			if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) {
				buffer += ".0";
			}

			// strip the zero padding from the right
			if (precisionType == PrecisionType::decimalPlaces) {
				buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision),
					buffer.end());
			}

			return buffer;
		}
	} // namespace

	String valueToString(double value, unsigned int precision,
		PrecisionType precisionType) {
		return valueToString(value, false, precision, precisionType);
	}

	String valueToString(bool value) { return value ? "true" : "false"; }

	static bool doesAnyCharRequireEscaping(char const* s, size_t n) {
		assert(s || !n);

		return std::any_of(s, s + n, [](unsigned char c) {
			return c == '\\' || c == '"' || c < 0x20 || c > 0x7F;
			});
	}

	static unsigned int utf8ToCodepoint(const char*& s, const char* e) {
		const unsigned int REPLACEMENT_CHARACTER = 0xFFFD;

		unsigned int firstByte = static_cast<unsigned char>(*s);

		if (firstByte < 0x80)
			return firstByte;

		if (firstByte < 0xE0) {
			if (e - s < 2)
				return REPLACEMENT_CHARACTER;

			unsigned int calculated =
				((firstByte & 0x1F) << 6) | (static_cast<unsigned int>(s[1]) & 0x3F);
			s += 1;
			// oversized encoded characters are invalid
			return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated;
		}

		if (firstByte < 0xF0) {
			if (e - s < 3)
				return REPLACEMENT_CHARACTER;

			unsigned int calculated = ((firstByte & 0x0F) << 12) |
				((static_cast<unsigned int>(s[1]) & 0x3F) << 6) |
				(static_cast<unsigned int>(s[2]) & 0x3F);
			s += 2;
			// surrogates aren't valid codepoints itself
			// shouldn't be UTF-8 encoded
			if (calculated >= 0xD800 && calculated <= 0xDFFF)
				return REPLACEMENT_CHARACTER;
			// oversized encoded characters are invalid
			return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated;
		}

		if (firstByte < 0xF8) {
			if (e - s < 4)
				return REPLACEMENT_CHARACTER;

			unsigned int calculated = ((firstByte & 0x07) << 18) |
				((static_cast<unsigned int>(s[1]) & 0x3F) << 12) |
				((static_cast<unsigned int>(s[2]) & 0x3F) << 6) |
				(static_cast<unsigned int>(s[3]) & 0x3F);
			s += 3;
			// oversized encoded characters are invalid
			return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated;
		}

		return REPLACEMENT_CHARACTER;
	}

	static const char hex2[] = "000102030405060708090a0b0c0d0e0f"
		"101112131415161718191a1b1c1d1e1f"
		"202122232425262728292a2b2c2d2e2f"
		"303132333435363738393a3b3c3d3e3f"
		"404142434445464748494a4b4c4d4e4f"
		"505152535455565758595a5b5c5d5e5f"
		"606162636465666768696a6b6c6d6e6f"
		"707172737475767778797a7b7c7d7e7f"
		"808182838485868788898a8b8c8d8e8f"
		"909192939495969798999a9b9c9d9e9f"
		"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
		"b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
		"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
		"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
		"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
		"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";

	static String toHex16Bit(unsigned int x) {
		const unsigned int hi = (x >> 8) & 0xff;
		const unsigned int lo = x & 0xff;
		String result(4, ' ');
		result[0] = hex2[2 * hi];
		result[1] = hex2[2 * hi + 1];
		result[2] = hex2[2 * lo];
		result[3] = hex2[2 * lo + 1];
		return result;
	}

	static void appendRaw(String& result, unsigned ch) {
		result += static_cast<char>(ch);
	}

	static void appendHex(String& result, unsigned ch) {
		result.append("\\u").append(toHex16Bit(ch));
	}

	static String valueToQuotedStringN(const char* value, size_t length,
		bool emitUTF8 = false) {
		if (value == nullptr)
			return "";

		if (!doesAnyCharRequireEscaping(value, length))
			return String("\"") + value + "\"";
		// We have to walk value and escape any special characters.
		// Appending to String is not efficient, but this should be rare.
		// (Note: forward slashes are *not* rare, but I am not escaping them.)
		String::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL
		String result;
		result.reserve(maxsize); // to avoid lots of mallocs
		result += "\"";
		char const* end = value + length;
		for (const char* c = value; c != end; ++c) {
			switch (*c) {
			case '\"':
				result += "\\\"";
				break;
			case '\\':
				result += "\\\\";
				break;
			case '\b':
				result += "\\b";
				break;
			case '\f':
				result += "\\f";
				break;
			case '\n':
				result += "\\n";
				break;
			case '\r':
				result += "\\r";
				break;
			case '\t':
				result += "\\t";
				break;
				// case '/':
				// Even though \/ is considered a legal escape in JSON, a bare
				// slash is also legal, so I see no reason to escape it.
				// (I hope I am not misunderstanding something.)
				// blep notes: actually escaping \/ may be useful in javascript to avoid </
				// sequence.
				// Should add a flag to allow this compatibility mode and prevent this
				// sequence from occurring.
			default: {
				if (emitUTF8) {
					unsigned codepoint = static_cast<unsigned char>(*c);
					if (codepoint < 0x20) {
						appendHex(result, codepoint);
					}
					else {
						appendRaw(result, codepoint);
					}
				}
				else {
					unsigned codepoint = utf8ToCodepoint(c, end); // modifies `c`
					if (codepoint < 0x20) {
						appendHex(result, codepoint);
					}
					else if (codepoint < 0x80) {
						appendRaw(result, codepoint);
					}
					else if (codepoint < 0x10000) {
						// Basic Multilingual Plane
						appendHex(result, codepoint);
					}
					else {
						// Extended Unicode. Encode 20 bits as a surrogate pair.
						codepoint -= 0x10000;
						appendHex(result, 0xd800 + ((codepoint >> 10) & 0x3ff));
						appendHex(result, 0xdc00 + (codepoint & 0x3ff));
					}
				}
			} break;
			}
		}
		result += "\"";
		return result;
	}

	String valueToQuotedString(const char* value) {
		return valueToQuotedStringN(value, strlen(value));
	}

	// Class Writer
	// //////////////////////////////////////////////////////////////////
	Writer::~Writer() = default;

	// Class FastWriter
	// //////////////////////////////////////////////////////////////////

	FastWriter::FastWriter()

		= default;

	void FastWriter::enableYAMLCompatibility() { yamlCompatibilityEnabled_ = true; }

	void FastWriter::dropNullPlaceholders() { dropNullPlaceholders_ = true; }

	void FastWriter::omitEndingLineFeed() { omitEndingLineFeed_ = true; }

	String FastWriter::write(const Value& root) {
		document_.clear();
		writeValue(root);
		if (!omitEndingLineFeed_)
			document_ += '\n';
		return document_;
	}

	void FastWriter::writeValue(const Value& value) {
		switch (value.type()) {
		case nullValue:
			if (!dropNullPlaceholders_)
				document_ += "null";
			break;
		case intValue:
			document_ += valueToString(value.asLargestInt());
			break;
		case uintValue:
			document_ += valueToString(value.asLargestUInt());
			break;
		case realValue:
			document_ += valueToString(value.asDouble());
			break;
		case stringValue: {
			// Is NULL possible for value.string_? No.
			char const* str;
			char const* end;
			bool ok = value.getString(&str, &end);
			if (ok)
				document_ += valueToQuotedStringN(str, static_cast<size_t>(end - str));
			break;
		}
		case booleanValue:
			document_ += valueToString(value.asBool());
			break;
		case arrayValue: {
			document_ += '[';
			ArrayIndex size = value.size();
			for (ArrayIndex index = 0; index < size; ++index) {
				if (index > 0)
					document_ += ',';
				writeValue(value[index]);
			}
			document_ += ']';
		} break;
		case objectValue: {
			Value::Members members(value.getMemberNames());
			document_ += '{';
			for (auto it = members.begin(); it != members.end(); ++it) {
				const String& name = *it;
				if (it != members.begin())
					document_ += ',';
				document_ += valueToQuotedStringN(name.data(), name.length());
				document_ += yamlCompatibilityEnabled_ ? ": " : ":";
				writeValue(value[name]);
			}
			document_ += '}';
		} break;
		}
	}

	// Class StyledWriter
	// //////////////////////////////////////////////////////////////////

	StyledWriter::StyledWriter() = default;

	String StyledWriter::write(const Value& root) {
		document_.clear();
		addChildValues_ = false;
		indentString_.clear();
		writeCommentBeforeValue(root);
		writeValue(root);
		writeCommentAfterValueOnSameLine(root);
		document_ += '\n';
		return document_;
	}

	void StyledWriter::writeValue(const Value& value) {
		switch (value.type()) {
		case nullValue:
			pushValue("null");
			break;
		case intValue:
			pushValue(valueToString(value.asLargestInt()));
			break;
		case uintValue:
			pushValue(valueToString(value.asLargestUInt()));
			break;
		case realValue:
			pushValue(valueToString(value.asDouble()));
			break;
		case stringValue: {
			// Is NULL possible for value.string_? No.
			char const* str;
			char const* end;
			bool ok = value.getString(&str, &end);
			if (ok)
				pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
			else
				pushValue("");
			break;
		}
		case booleanValue:
			pushValue(valueToString(value.asBool()));
			break;
		case arrayValue:
			writeArrayValue(value);
			break;
		case objectValue: {
			Value::Members members(value.getMemberNames());
			if (members.empty())
				pushValue("{}");
			else {
				writeWithIndent("{");
				indent();
				auto it = members.begin();
				for (;;) {
					const String& name = *it;
					const Value& childValue = value[name];
					writeCommentBeforeValue(childValue);
					writeWithIndent(valueToQuotedString(name.c_str()));
					document_ += " : ";
					writeValue(childValue);
					if (++it == members.end()) {
						writeCommentAfterValueOnSameLine(childValue);
						break;
					}
					document_ += ',';
					writeCommentAfterValueOnSameLine(childValue);
				}
				unindent();
				writeWithIndent("}");
			}
		} break;
		}
	}

	void StyledWriter::writeArrayValue(const Value& value) {
		size_t size = value.size();
		if (size == 0)
			pushValue("[]");
		else {
			bool isArrayMultiLine = isMultilineArray(value);
			if (isArrayMultiLine) {
				writeWithIndent("[");
				indent();
				bool hasChildValue = !childValues_.empty();
				ArrayIndex index = 0;
				for (;;) {
					const Value& childValue = value[index];
					writeCommentBeforeValue(childValue);
					if (hasChildValue)
						writeWithIndent(childValues_[index]);
					else {
						writeIndent();
						writeValue(childValue);
					}
					if (++index == size) {
						writeCommentAfterValueOnSameLine(childValue);
						break;
					}
					document_ += ',';
					writeCommentAfterValueOnSameLine(childValue);
				}
				unindent();
				writeWithIndent("]");
			}
			else // output on a single line
			{
				assert(childValues_.size() == size);
				document_ += "[ ";
				for (size_t index = 0; index < size; ++index) {
					if (index > 0)
						document_ += ", ";
					document_ += childValues_[index];
				}
				document_ += " ]";
			}
		}
	}

	bool StyledWriter::isMultilineArray(const Value& value) {
		ArrayIndex const size = value.size();
		bool isMultiLine = size * 3 >= rightMargin_;
		childValues_.clear();
		for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
			const Value& childValue = value[index];
			isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
				!childValue.empty());
		}
		if (!isMultiLine) // check if line length > max line length
		{
			childValues_.reserve(size);
			addChildValues_ = true;
			ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
			for (ArrayIndex index = 0; index < size; ++index) {
				if (hasCommentForValue(value[index])) {
					isMultiLine = true;
				}
				writeValue(value[index]);
				lineLength += static_cast<ArrayIndex>(childValues_[index].length());
			}
			addChildValues_ = false;
			isMultiLine = isMultiLine || lineLength >= rightMargin_;
		}
		return isMultiLine;
	}

	void StyledWriter::pushValue(const String& value) {
		if (addChildValues_)
			childValues_.push_back(value);
		else
			document_ += value;
	}

	void StyledWriter::writeIndent() {
		if (!document_.empty()) {
			char last = document_[document_.length() - 1];
			if (last == ' ') // already indented
				return;
			if (last != '\n') // Comments may add new-line
				document_ += '\n';
		}
		document_ += indentString_;
	}

	void StyledWriter::writeWithIndent(const String& value) {
		writeIndent();
		document_ += value;
	}

	void StyledWriter::indent() { indentString_ += String(indentSize_, ' '); }

	void StyledWriter::unindent() {
		assert(indentString_.size() >= indentSize_);
		indentString_.resize(indentString_.size() - indentSize_);
	}

	void StyledWriter::writeCommentBeforeValue(const Value& root) {
		if (!root.hasComment(commentBefore))
			return;

		document_ += '\n';
		writeIndent();
		const String& comment = root.getComment(commentBefore);
		String::const_iterator iter = comment.begin();
		while (iter != comment.end()) {
			document_ += *iter;
			if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
				writeIndent();
			++iter;
		}

		// Comments are stripped of trailing newlines, so add one here
		document_ += '\n';
	}

	void StyledWriter::writeCommentAfterValueOnSameLine(const Value& root) {
		if (root.hasComment(commentAfterOnSameLine))
			document_ += " " + root.getComment(commentAfterOnSameLine);

		if (root.hasComment(commentAfter)) {
			document_ += '\n';
			document_ += root.getComment(commentAfter);
			document_ += '\n';
		}
	}

	bool StyledWriter::hasCommentForValue(const Value& value) {
		return value.hasComment(commentBefore) ||
			value.hasComment(commentAfterOnSameLine) ||
			value.hasComment(commentAfter);
	}

	// Class StyledStreamWriter
	// //////////////////////////////////////////////////////////////////

	StyledStreamWriter::StyledStreamWriter(String indentation)
		: document_(nullptr), indentation_(std::move(indentation)),
		addChildValues_(), indented_(false) {}

	void StyledStreamWriter::write(OStream& out, const Value& root) {
		document_ = &out;
		addChildValues_ = false;
		indentString_.clear();
		indented_ = true;
		writeCommentBeforeValue(root);
		if (!indented_)
			writeIndent();
		indented_ = true;
		writeValue(root);
		writeCommentAfterValueOnSameLine(root);
		*document_ << "\n";
		document_ = nullptr; // Forget the stream, for safety.
	}

	void StyledStreamWriter::writeValue(const Value& value) {
		switch (value.type()) {
		case nullValue:
			pushValue("null");
			break;
		case intValue:
			pushValue(valueToString(value.asLargestInt()));
			break;
		case uintValue:
			pushValue(valueToString(value.asLargestUInt()));
			break;
		case realValue:
			pushValue(valueToString(value.asDouble()));
			break;
		case stringValue: {
			// Is NULL possible for value.string_? No.
			char const* str;
			char const* end;
			bool ok = value.getString(&str, &end);
			if (ok)
				pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
			else
				pushValue("");
			break;
		}
		case booleanValue:
			pushValue(valueToString(value.asBool()));
			break;
		case arrayValue:
			writeArrayValue(value);
			break;
		case objectValue: {
			Value::Members members(value.getMemberNames());
			if (members.empty())
				pushValue("{}");
			else {
				writeWithIndent("{");
				indent();
				auto it = members.begin();
				for (;;) {
					const String& name = *it;
					const Value& childValue = value[name];
					writeCommentBeforeValue(childValue);
					writeWithIndent(valueToQuotedString(name.c_str()));
					*document_ << " : ";
					writeValue(childValue);
					if (++it == members.end()) {
						writeCommentAfterValueOnSameLine(childValue);
						break;
					}
					*document_ << ",";
					writeCommentAfterValueOnSameLine(childValue);
				}
				unindent();
				writeWithIndent("}");
			}
		} break;
		}
	}

	void StyledStreamWriter::writeArrayValue(const Value& value) {
		unsigned size = value.size();
		if (size == 0)
			pushValue("[]");
		else {
			bool isArrayMultiLine = isMultilineArray(value);
			if (isArrayMultiLine) {
				writeWithIndent("[");
				indent();
				bool hasChildValue = !childValues_.empty();
				unsigned index = 0;
				for (;;) {
					const Value& childValue = value[index];
					writeCommentBeforeValue(childValue);
					if (hasChildValue)
						writeWithIndent(childValues_[index]);
					else {
						if (!indented_)
							writeIndent();
						indented_ = true;
						writeValue(childValue);
						indented_ = false;
					}
					if (++index == size) {
						writeCommentAfterValueOnSameLine(childValue);
						break;
					}
					*document_ << ",";
					writeCommentAfterValueOnSameLine(childValue);
				}
				unindent();
				writeWithIndent("]");
			}
			else // output on a single line
			{
				assert(childValues_.size() == size);
				*document_ << "[ ";
				for (unsigned index = 0; index < size; ++index) {
					if (index > 0)
						*document_ << ", ";
					*document_ << childValues_[index];
				}
				*document_ << " ]";
			}
		}
	}

	bool StyledStreamWriter::isMultilineArray(const Value& value) {
		ArrayIndex const size = value.size();
		bool isMultiLine = size * 3 >= rightMargin_;
		childValues_.clear();
		for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
			const Value& childValue = value[index];
			isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
				!childValue.empty());
		}
		if (!isMultiLine) // check if line length > max line length
		{
			childValues_.reserve(size);
			addChildValues_ = true;
			ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
			for (ArrayIndex index = 0; index < size; ++index) {
				if (hasCommentForValue(value[index])) {
					isMultiLine = true;
				}
				writeValue(value[index]);
				lineLength += static_cast<ArrayIndex>(childValues_[index].length());
			}
			addChildValues_ = false;
			isMultiLine = isMultiLine || lineLength >= rightMargin_;
		}
		return isMultiLine;
	}

	void StyledStreamWriter::pushValue(const String& value) {
		if (addChildValues_)
			childValues_.push_back(value);
		else
			*document_ << value;
	}

	void StyledStreamWriter::writeIndent() {
		// blep intended this to look at the so-far-written string
		// to determine whether we are already indented, but
		// with a stream we cannot do that. So we rely on some saved state.
		// The caller checks indented_.
		*document_ << '\n' << indentString_;
	}

	void StyledStreamWriter::writeWithIndent(const String& value) {
		if (!indented_)
			writeIndent();
		*document_ << value;
		indented_ = false;
	}

	void StyledStreamWriter::indent() { indentString_ += indentation_; }

	void StyledStreamWriter::unindent() {
		assert(indentString_.size() >= indentation_.size());
		indentString_.resize(indentString_.size() - indentation_.size());
	}

	void StyledStreamWriter::writeCommentBeforeValue(const Value& root) {
		if (!root.hasComment(commentBefore))
			return;

		if (!indented_)
			writeIndent();
		const String& comment = root.getComment(commentBefore);
		String::const_iterator iter = comment.begin();
		while (iter != comment.end()) {
			*document_ << *iter;
			if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
				// writeIndent();  // would include newline
				*document_ << indentString_;
			++iter;
		}
		indented_ = false;
	}

	void StyledStreamWriter::writeCommentAfterValueOnSameLine(const Value& root) {
		if (root.hasComment(commentAfterOnSameLine))
			*document_ << ' ' << root.getComment(commentAfterOnSameLine);

		if (root.hasComment(commentAfter)) {
			writeIndent();
			*document_ << root.getComment(commentAfter);
		}
		indented_ = false;
	}

	bool StyledStreamWriter::hasCommentForValue(const Value& value) {
		return value.hasComment(commentBefore) ||
			value.hasComment(commentAfterOnSameLine) ||
			value.hasComment(commentAfter);
	}

	//////////////////////////
	// BuiltStyledStreamWriter

	/// Scoped enums are not available until C++11.
	struct CommentStyle {
		/// Decide whether to write comments.
		enum Enum {
			None, ///< Drop all comments.
			Most, ///< Recover odd behavior of previous versions (not implemented yet).
			All   ///< Keep all comments.
		};
	};

	struct BuiltStyledStreamWriter : public StreamWriter {
		BuiltStyledStreamWriter(String indentation, CommentStyle::Enum cs,
			String colonSymbol, String nullSymbol,
			String endingLineFeedSymbol, bool useSpecialFloats,
			bool emitUTF8, unsigned int precision,
			PrecisionType precisionType);
		int write(Value const& root, OStream* sout) override;

	private:
		void writeValue(Value const& value);
		void writeArrayValue(Value const& value);
		bool isMultilineArray(Value const& value);
		void pushValue(String const& value);
		void writeIndent();
		void writeWithIndent(String const& value);
		void indent();
		void unindent();
		void writeCommentBeforeValue(Value const& root);
		void writeCommentAfterValueOnSameLine(Value const& root);
		static bool hasCommentForValue(const Value& value);

		using ChildValues = std::vector<String>;

		ChildValues childValues_;
		String indentString_;
		unsigned int rightMargin_;
		String indentation_;
		CommentStyle::Enum cs_;
		String colonSymbol_;
		String nullSymbol_;
		String endingLineFeedSymbol_;
		bool addChildValues_ : 1;
		bool indented_ : 1;
		bool useSpecialFloats_ : 1;
		bool emitUTF8_ : 1;
		unsigned int precision_;
		PrecisionType precisionType_;
	};
	BuiltStyledStreamWriter::BuiltStyledStreamWriter(
		String indentation, CommentStyle::Enum cs, String colonSymbol,
		String nullSymbol, String endingLineFeedSymbol, bool useSpecialFloats,
		bool emitUTF8, unsigned int precision, PrecisionType precisionType)
		: rightMargin_(74), indentation_(std::move(indentation)), cs_(cs),
		colonSymbol_(std::move(colonSymbol)), nullSymbol_(std::move(nullSymbol)),
		endingLineFeedSymbol_(std::move(endingLineFeedSymbol)),
		addChildValues_(false), indented_(false),
		useSpecialFloats_(useSpecialFloats), emitUTF8_(emitUTF8),
		precision_(precision), precisionType_(precisionType) {}
	int BuiltStyledStreamWriter::write(Value const& root, OStream* sout) {
		sout_ = sout;
		addChildValues_ = false;
		indented_ = true;
		indentString_.clear();
		writeCommentBeforeValue(root);
		if (!indented_)
			writeIndent();
		indented_ = true;
		writeValue(root);
		writeCommentAfterValueOnSameLine(root);
		*sout_ << endingLineFeedSymbol_;
		sout_ = nullptr;
		return 0;
	}
	void BuiltStyledStreamWriter::writeValue(Value const& value) {
		switch (value.type()) {
		case nullValue:
			pushValue(nullSymbol_);
			break;
		case intValue:
			pushValue(valueToString(value.asLargestInt()));
			break;
		case uintValue:
			pushValue(valueToString(value.asLargestUInt()));
			break;
		case realValue:
			pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_,
				precisionType_));
			break;
		case stringValue: {
			// Is NULL is possible for value.string_? No.
			char const* str;
			char const* end;
			bool ok = value.getString(&str, &end);
			if (ok)
				pushValue(
					valueToQuotedStringN(str, static_cast<size_t>(end - str), emitUTF8_));
			else
				pushValue("");
			break;
		}
		case booleanValue:
			pushValue(valueToString(value.asBool()));
			break;
		case arrayValue:
			writeArrayValue(value);
			break;
		case objectValue: {
			Value::Members members(value.getMemberNames());
			if (members.empty())
				pushValue("{}");
			else {
				writeWithIndent("{");
				indent();
				auto it = members.begin();
				for (;;) {
					String const& name = *it;
					Value const& childValue = value[name];
					writeCommentBeforeValue(childValue);
					writeWithIndent(
						valueToQuotedStringN(name.data(), name.length(), emitUTF8_));
					*sout_ << colonSymbol_;
					writeValue(childValue);
					if (++it == members.end()) {
						writeCommentAfterValueOnSameLine(childValue);
						break;
					}
					*sout_ << ",";
					writeCommentAfterValueOnSameLine(childValue);
				}
				unindent();
				writeWithIndent("}");
			}
		} break;
		}
	}

	void BuiltStyledStreamWriter::writeArrayValue(Value const& value) {
		unsigned size = value.size();
		if (size == 0)
			pushValue("[]");
		else {
			bool isMultiLine = (cs_ == CommentStyle::All) || isMultilineArray(value);
			if (isMultiLine) {
				writeWithIndent("[");
				indent();
				bool hasChildValue = !childValues_.empty();
				unsigned index = 0;
				for (;;) {
					Value const& childValue = value[index];
					writeCommentBeforeValue(childValue);
					if (hasChildValue)
						writeWithIndent(childValues_[index]);
					else {
						if (!indented_)
							writeIndent();
						indented_ = true;
						writeValue(childValue);
						indented_ = false;
					}
					if (++index == size) {
						writeCommentAfterValueOnSameLine(childValue);
						break;
					}
					*sout_ << ",";
					writeCommentAfterValueOnSameLine(childValue);
				}
				unindent();
				writeWithIndent("]");
			}
			else // output on a single line
			{
				assert(childValues_.size() == size);
				*sout_ << "[";
				if (!indentation_.empty())
					*sout_ << " ";
				for (unsigned index = 0; index < size; ++index) {
					if (index > 0)
						*sout_ << ((!indentation_.empty()) ? ", " : ",");
					*sout_ << childValues_[index];
				}
				if (!indentation_.empty())
					*sout_ << " ";
				*sout_ << "]";
			}
		}
	}

	bool BuiltStyledStreamWriter::isMultilineArray(Value const& value) {
		ArrayIndex const size = value.size();
		bool isMultiLine = size * 3 >= rightMargin_;
		childValues_.clear();
		for (ArrayIndex index = 0; index < size && !isMultiLine; ++index) {
			Value const& childValue = value[index];
			isMultiLine = ((childValue.isArray() || childValue.isObject()) &&
				!childValue.empty());
		}
		if (!isMultiLine) // check if line length > max line length
		{
			childValues_.reserve(size);
			addChildValues_ = true;
			ArrayIndex lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
			for (ArrayIndex index = 0; index < size; ++index) {
				if (hasCommentForValue(value[index])) {
					isMultiLine = true;
				}
				writeValue(value[index]);
				lineLength += static_cast<ArrayIndex>(childValues_[index].length());
			}
			addChildValues_ = false;
			isMultiLine = isMultiLine || lineLength >= rightMargin_;
		}
		return isMultiLine;
	}

	void BuiltStyledStreamWriter::pushValue(String const& value) {
		if (addChildValues_)
			childValues_.push_back(value);
		else
			*sout_ << value;
	}

	void BuiltStyledStreamWriter::writeIndent() {
		// blep intended this to look at the so-far-written string
		// to determine whether we are already indented, but
		// with a stream we cannot do that. So we rely on some saved state.
		// The caller checks indented_.

		if (!indentation_.empty()) {
			// In this case, drop newlines too.
			*sout_ << '\n' << indentString_;
		}
	}

	void BuiltStyledStreamWriter::writeWithIndent(String const& value) {
		if (!indented_)
			writeIndent();
		*sout_ << value;
		indented_ = false;
	}

	void BuiltStyledStreamWriter::indent() { indentString_ += indentation_; }

	void BuiltStyledStreamWriter::unindent() {
		assert(indentString_.size() >= indentation_.size());
		indentString_.resize(indentString_.size() - indentation_.size());
	}

	void BuiltStyledStreamWriter::writeCommentBeforeValue(Value const& root) {
		if (cs_ == CommentStyle::None)
			return;
		if (!root.hasComment(commentBefore))
			return;

		if (!indented_)
			writeIndent();
		const String& comment = root.getComment(commentBefore);
		String::const_iterator iter = comment.begin();
		while (iter != comment.end()) {
			*sout_ << *iter;
			if (*iter == '\n' && ((iter + 1) != comment.end() && *(iter + 1) == '/'))
				// writeIndent();  // would write extra newline
				*sout_ << indentString_;
			++iter;
		}
		indented_ = false;
	}

	void BuiltStyledStreamWriter::writeCommentAfterValueOnSameLine(
		Value const& root) {
		if (cs_ == CommentStyle::None)
			return;
		if (root.hasComment(commentAfterOnSameLine))
			*sout_ << " " + root.getComment(commentAfterOnSameLine);

		if (root.hasComment(commentAfter)) {
			writeIndent();
			*sout_ << root.getComment(commentAfter);
		}
	}

	// static
	bool BuiltStyledStreamWriter::hasCommentForValue(const Value& value) {
		return value.hasComment(commentBefore) ||
			value.hasComment(commentAfterOnSameLine) ||
			value.hasComment(commentAfter);
	}

	///////////////
	// StreamWriter

	StreamWriter::StreamWriter() : sout_(nullptr) {}
	StreamWriter::~StreamWriter() = default;
	StreamWriter::Factory::~Factory() = default;
	StreamWriterBuilder::StreamWriterBuilder() { setDefaults(&settings_); }
	StreamWriterBuilder::~StreamWriterBuilder() = default;
	StreamWriter* StreamWriterBuilder::newStreamWriter() const {
		const String indentation = settings_["indentation"].asString();
		const String cs_str = settings_["commentStyle"].asString();
		const String pt_str = settings_["precisionType"].asString();
		const bool eyc = settings_["enableYAMLCompatibility"].asBool();
		const bool dnp = settings_["dropNullPlaceholders"].asBool();
		const bool usf = settings_["useSpecialFloats"].asBool();
		const bool emitUTF8 = settings_["emitUTF8"].asBool();
		unsigned int pre = settings_["precision"].asUInt();
		CommentStyle::Enum cs = CommentStyle::All;
		if (cs_str == "All") {
			cs = CommentStyle::All;
		}
		else if (cs_str == "None") {
			cs = CommentStyle::None;
		}
		else {
			throwRuntimeError("commentStyle must be 'All' or 'None'");
		}
		PrecisionType precisionType(significantDigits);
		if (pt_str == "significant") {
			precisionType = PrecisionType::significantDigits;
		}
		else if (pt_str == "decimal") {
			precisionType = PrecisionType::decimalPlaces;
		}
		else {
			throwRuntimeError("precisionType must be 'significant' or 'decimal'");
		}
		String colonSymbol = " : ";
		if (eyc) {
			colonSymbol = ": ";
		}
		else if (indentation.empty()) {
			colonSymbol = ":";
		}
		String nullSymbol = "null";
		if (dnp) {
			nullSymbol.clear();
		}
		if (pre > 17)
			pre = 17;
		String endingLineFeedSymbol;
		return new BuiltStyledStreamWriter(indentation, cs, colonSymbol, nullSymbol,
			endingLineFeedSymbol, usf, emitUTF8, pre,
			precisionType);
	}

	bool StreamWriterBuilder::validate(Json::Value* invalid) const {
		static const auto& valid_keys = *new std::set<String>{
			"indentation",
			"commentStyle",
			"enableYAMLCompatibility",
			"dropNullPlaceholders",
			"useSpecialFloats",
			"emitUTF8",
			"precision",
			"precisionType",
		};
		for (auto si = settings_.begin(); si != settings_.end(); ++si) {
			auto key = si.name();
			if (valid_keys.count(key))
				continue;
			if (invalid)
				(*invalid)[key] = *si;
			else
				return false;
		}
		return invalid ? invalid->empty() : true;
	}

	Value& StreamWriterBuilder::operator[](const String& key) {
		return settings_[key];
	}
	// static
	void StreamWriterBuilder::setDefaults(Json::Value* settings) {
		//! [StreamWriterBuilderDefaults]
		(*settings)["commentStyle"] = "All";
		(*settings)["indentation"] = "\t";
		(*settings)["enableYAMLCompatibility"] = false;
		(*settings)["dropNullPlaceholders"] = false;
		(*settings)["useSpecialFloats"] = false;
		(*settings)["emitUTF8"] = false;
		(*settings)["precision"] = 17;
		(*settings)["precisionType"] = "significant";
		//! [StreamWriterBuilderDefaults]
	}

	String writeString(StreamWriter::Factory const& factory, Value const& root) {
		OStringStream sout;
		StreamWriterPtr const writer(factory.newStreamWriter());
		writer->write(root, &sout);
		return sout.str();
	}

	OStream& operator<<(OStream& sout, Value const& root) {
		StreamWriterBuilder builder;
		StreamWriterPtr const writer(builder.newStreamWriter());
		writer->write(root, &sout);
		return sout;
	}
} // namespace Json